基于Docker托管Azure DevOps代理

基于Docker托管Azure DevOps代理

​ Azure DevOps非常好用,但是为代理准备单独的服务器经常会显得性价比不高:配置低了,前端构建时会教会你做人,配置太高又有点浪费资源,代理数量少了各团队构建要打架。对于既想享受DevOps的美妙之处但是资源捉襟见肘的小公司,真是一件比较头痛的事情。那么有没有更具备性价比的方案呢?那自然是有的,那就是基于Docker进行自托管。

​ 这里不建议大家丢k8s集群里面去,一是会抢资源,二是k8s容器运行时不推荐大家使用docker,三是不安全。那么现在我们就可以准备一台好点的服务器,来基于Docker来托管自有的Azure DevOps代理。

编写Dockerfile

​ 废话不多,建议大家直接抄代码吧:

FROM ubuntu:20.04
RUN DEBIAN_FRONTEND=noninteractive apt-get update
RUN DEBIAN_FRONTEND=noninteractive apt-get upgrade -y

RUN DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends \
    apt-transport-https \
    apt-utils \
    ca-certificates \
    curl \
    git \
    iputils-ping \
    jq \
    lsb-release \
    software-properties-common \
    gss-ntlmssp

RUN curl -sL https://aka.ms/InstallAzureCLIDeb | bash

# Can be 'linux-x64', 'linux-arm64', 'linux-arm', 'rhel.6-x64'.
ENV TARGETARCH=linux-x64

WORKDIR /azp
COPY ./start.sh .
COPY ./vsts-agent-linux-x64-2.181.2.tar.gz .
RUN chmod +x start.sh

ENTRYPOINT [ "./start.sh" ]

​ 如果对Dockerfile不理解,可以参考笔者之前的教程、博客和书籍《Docker+Kubernetes应用开发与快速上云》来解决问题以及查看日志。这里不建议大家使用官方教程,基本上走不通还问题颇多。

值得注意的是,上面涉及到了两个文件:

  1. start.sh
  2. vsts-agent-linux-x64-2.181.2.tar.gz(从Azure DevOps管理界面下载)

start.sh脚本内容参考如下:

#!/bin/bash
set -e

if [ -z "$AZP_URL" ]; then
  echo 1>&2 "error: missing AZP_URL environment variable"
  exit 1
fi

if [ -z "$AZP_TOKEN_FILE" ]; then
  if [ -z "$AZP_TOKEN" ]; then
    echo 1>&2 "error: missing AZP_TOKEN environment variable"
    exit 1
  fi

  AZP_TOKEN_FILE=/azp/.token
  echo -n $AZP_TOKEN > "$AZP_TOKEN_FILE"
fi

unset AZP_TOKEN

if [ -n "$AZP_WORK" ]; then
  mkdir -p "$AZP_WORK"
fi

export AGENT_ALLOW_RUNASROOT="1"

cleanup() {
  if [ -e config.sh ]; then
    print_header "Cleanup. Removing Azure Pipelines agent..."

    # If the agent has some running jobs, the configuration removal process will fail.
    # So, give it some time to finish the job.
    while true; do
      ./config.sh remove --unattended --auth negotiate --userName build --password $(cat "$AZP_TOKEN_FILE") && break

      echo "Retrying in 30 seconds..."
      sleep 30
    done
  fi
}

print_header() {
  lightcyan='\033[1;36m'
  nocolor='\033[0m'
  echo -e "${lightcyan}$1${nocolor}"
}

# Let the agent ignore the token env variables
export VSO_AGENT_IGNORE=AZP_TOKEN,AZP_TOKEN_FILE

print_header "1. Determining matching Azure Pipelines agent..."

print_header "url=$AZP_URL/_apis/distributedtask/packages/agent?platform=$TARGETARCH&top=1"

# AZP_AGENT_PACKAGES=$(curl -LsS \
#     -u user:$(cat "$AZP_TOKEN_FILE") \
#     -H 'Accept:application/json;' \
#     "$AZP_URL/_apis/distributedtask/packages/agent?platform=$TARGETARCH&top=1")

AZP_AGENT_PACKAGES=$(curl -LsS \
    --ntlm \
    -u build:$(cat "$AZP_TOKEN_FILE") \
    -H 'Accept:application/json;' \
    "$AZP_URL/_apis/distributedtask/packages/agent?platform=$TARGETARCH&top=1")

    print_header "$AZP_AGENT_PACKAGES"

AZP_AGENT_PACKAGE_LATEST_URL=$(echo "$AZP_AGENT_PACKAGES" | jq -r '.value[0].downloadUrl')

    # print_header "1.2 AZP_AGENT_PACKAGE_LATEST_URL=$AZP_AGENT_PACKAGE_LATEST_URL..."

if [ -z "$AZP_AGENT_PACKAGE_LATEST_URL" -o "$AZP_AGENT_PACKAGE_LATEST_URL" == "null" ]; then
  echo 1>&2 "error: could not determine a matching Azure Pipelines agent"
  echo 1>&2 "check that account '$AZP_URL' is correct and the token is valid for that account"
  exit 1
fi

print_header "2. extracting Azure Pipelines agent..."

tar zxf ./vsts-agent-linux-x64-2.181.2.tar.gz & wait $!
#curl -LsS $AZP_AGENT_PACKAGE_LATEST_URL | tar -xz & wait $!

source ./env.sh

print_header "3. Configuring Azure Pipelines agent..."

./config.sh --unattended \
  --agent "${AZP_AGENT_NAME:-$(hostname)}" \
  --url "$AZP_URL" \
  --auth negotiate \
  --userName build \
  --password $(cat "$AZP_TOKEN_FILE") \
  --pool "${AZP_POOL:-Default}" \
  --work "${AZP_WORK:-_work}" \
  --replace \
  --acceptTeeEula & wait $!

print_header "4. Running Azure Pipelines agent..."

trap 'cleanup; exit 0' EXIT
trap 'cleanup; exit 130' INT
trap 'cleanup; exit 143' TERM

chmod +x ./run.sh

# To be aware of TERM and INT signals call run.sh
# Running it with the --once flag at the end will shut down the agent after the build is executed
./run.sh "$@" & wait $!

当然大家也可以直接使用我已经做好了的镜像:ccr.ccs.tencentyun.com/xinlai/tfsagnet:latest

这样,代码都不用抄了,多省事。相关环境变量如下所示:

环境变量 说明
AZP_URL Azure DevOps 或Azure DevOps Server实例的 URL。
AZP_TOKEN 密码。PAT认证有问题,被我改成了negotiate认证。
AZP_AGENT_NAME 代理名称 (默认值:容器主机名) 。
AZP_POOL 代理池名称 (默认值: Default) 。
AZP_WORK 工作目录 (默认值: _work) 。

在Docker中运行

参考脚本:

docker run -e AZP_URL=<Azure DevOps instance> -e AZP_TOKEN=<密码> -e AZP_AGENT_NAME=mydockeragent ccr.ccs.tencentyun.com/xinlai/tfsagnet:latest

但是现在的构建离不开Docker,那么Docker in Docker我们肯定是需要的,修改后参考脚本如下所示:

docker run --name docker-ag \
-e AZP_AGENT_NAME=docker-tx-ag \
-e AZP_URL= \
-e AZP_TOKEN=<密码> \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /usr/bin/docker:/usr/bin/docker \
--user root \
--restart=always \
ccr.ccs.tencentyun.com/xinlai/tfsagnet:latest

使用portainer来管理Docker代理

在一台服务器上跑了几个实例,为了便于管理,我们可以运行一个portainer实例来进行管理,参考脚本如下:

docker run -d -p 80:9000 --restart=always -v /var/run/docker.sock:/var/run/docker.sock --name prtainer portainer/portainer

然后我们就可以登录其Web管理界面来进行监控和管理:

portainer管理UI

我们可以在一台服务器多跑几个:

Docker代理

运行了之后,我们可以在Azure DevOps的默认代理池看到他们:

默认代理池

定时清理

​ 基于容器构建很方便,但是费空间,尤其是高频次构建,多运行时构建时,各种悬浮镜像的存在,我们可以加个定时构建任务来进行清理,参考脚本如下:

docker system prune -a -f

这样,50G的系统盘一拖3个代理,也是够了。

最后

至此,整个教程到此就结束了。服务器推荐配置如下所示:

  • 磁盘大小:30~50G,如代码文件过大可以适当增加空间以及增加自动清理的频率
  • 内存:16~32G,如果只构建后端代码,8G也是够了,但是前端构建是个黑洞,很多代码构建时没个16G内存,会直接原地崩溃
  • CPU:4~8核

 本篇
基于Docker托管Azure DevOps代理 基于Docker托管Azure DevOps代理
基于Docker托管Azure DevOps代理​ Azure DevOps非常好用,但是为代理准备单独的服务器经常会显得性价比不高:配置低了,前端构建时会教会你做人,配置太高又有点浪费资源,代理数量少了各团队构建要打架。对于既想享受
2022-12-18
下一篇 
Magicodes.Pay已支持Volo Abp Magicodes.Pay已支持Volo Abp
Magicodes.Pay已支持Volo Abp简介Magicodes.Pay希望打造一个统一支付库,相关库均使用.NET标准库编写,支持.NET Framework以及.NET Core。目前已提供Abp及Abp VNext(Volo A
  目录